package com.lucene.test.config;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import com.lucene.test.exception.LuceneException;
import com.lucene.test.field.BaseIndexField;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.*;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.nio.file.Paths;
import java.util.Collection;

/**
 * @author zoumy9
 * @date 2024/05/24 01:16
 **/
@Component
public class LuceneTemplate {
    private static final Logger logger = LoggerFactory.getLogger(LuceneTemplate.class);
    private IndexReader indexReader = null;
    private Analyzer analyzer;
    private Directory directory = null;

    @Value("${index.scan.dir: D:\\book\\lucene}")
    private String indexScanDir;
    @Value("${index.analyzer.impl:}")
    private String analyzerImpl;

    @PostConstruct
    private void init() {
        logger.info("start init LuceneTemplate");
        initDirectory();
        initialAnalyzer();
        logger.info("init LuceneTemplate success");
    }

    /**
     * 新增
     * @param doc
     */
    public void addDocument(IndexWriter indexWriter, Document doc) {
        try {
            indexWriter.addDocument(doc);
            indexWriter.commit();
        } catch (IOException e) {
            throw new LuceneException(e);
        }
    }

    /**
     * 新增
     * @param docs
     */
    public void addDocuments(Collection<Document> docs) {
        IndexWriter indexWriter = null;
        try {
            indexWriter = getIndexWriter(false);
            addDocuments(indexWriter, docs);
        } finally {
            closeIndexWriter(indexWriter);
        }
    }

    /**
     * 新增
     * @param isCreate
     * @param docs
     */
    public void addDocuments(Collection<Document> docs, boolean isCreate) {
        IndexWriter indexWriter = null;
        try {
            indexWriter = getIndexWriter(isCreate);
            addDocuments(indexWriter, docs);
        } finally {
            closeIndexWriter(indexWriter);
        }

    }

    /**
     * 新增
     * @param docs
     */
    public void addDocuments(IndexWriter indexWriter, Collection<Document> docs) {
        try {
            for(Document doc : docs) {
                indexWriter.addDocument(doc);
            }
            indexWriter.commit();
        } catch (IOException e) {
            rollBack(indexWriter);
            throw new LuceneException(e);
        }
    }

    /**
     * idKey不存在则新增
     * @param idKey
     * @param docs
     */
    public void updateDocuments(String idKey, Collection<Document> docs, boolean isCreate) {
        IndexWriter indexWriter = getIndexWriter(isCreate);
        try {
            for(Document doc : docs) {
                indexWriter.updateDocument(new Term(idKey, doc.get(idKey)), doc);
            }
            indexWriter.commit();
        } catch (IOException e) {
            rollBack(indexWriter);
            throw new LuceneException(e);
        } finally {
            closeIndexWriter(indexWriter);
        }
    }

    /**
     * idKey不存在则新增
     * @param idKey
     * @param newDoc
     */
    public void updateDocument(String idKey, Document newDoc) {
        IndexWriter indexWriter = getIndexWriter(false);
        try {
            indexWriter.updateDocument(buildTerm(idKey, newDoc.get(BaseIndexField.F_ID)), newDoc);
            indexWriter.commit();
        } catch (IOException e) {
            throw new LuceneException(e);
        } finally {
            closeIndexWriter(indexWriter);
        }
    }

    /**
     * 删除
     * @param idKey
     * @param idVal
     */
    public void deleteDocument(IndexWriter indexWriter,String idKey, String idVal) {
        try {
            indexWriter.deleteDocuments(new Term(idKey, idVal));
            indexWriter.commit();
        } catch (IOException e) {
            throw new LuceneException(e);
        }
    }

    /**
     * 初始化分词器
     */
    private void initialAnalyzer() {
        String analyzerClazz = ObjectUtil.defaultIfBlank(analyzerImpl,
                "org.apache.lucene.analysis.standard.StandardAnalyzer");
        Class<?> clazz;
        try {
            clazz = Class.forName(analyzerClazz);
            analyzer = (Analyzer) ReflectUtil.newInstance(clazz);
        } catch (ClassNotFoundException e) {
            logger.error("analyzerImpl:{} not found", analyzerClazz, e);
            analyzer = new StandardAnalyzer();
        }
    }

    /**
     * 获取Writer
     * @param create 初始化创建索引还是append
     * @return
     */
    public IndexWriter getIndexWriter(boolean create) {
        IndexWriterConfig iwc = new IndexWriterConfig(analyzer);
        if(create){
            iwc.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
        } else {
            iwc.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
        }
        IndexWriter indexWriter;
        try {
            indexWriter = new IndexWriter(directory, iwc);
        } catch (IOException e) {
            throw new LuceneException(e);
        }
        return indexWriter;
    }


    private void rollBack(IndexWriter indexWriter) {
        if(indexWriter != null) {
            try {
                indexWriter.rollback();
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }
    }

    /**
     * 关闭writer流
     * @param writer
     */
    public void closeIndexWriter(IndexWriter writer) {
        if(writer != null) {
            try {
                writer.close();
            } catch (IOException e) {
            }
        }
    }

    private void initDirectory() {
        if(StringUtils.isBlank(indexScanDir)) {
            logger.error("indexScanDir is null");
            throw new LuceneException("indexScanDir is null");
        }
        try {
            directory = FSDirectory.open(Paths.get(indexScanDir));
        } catch (IOException e) {
            logger.error("FSDirectory open path:{} failed", indexScanDir, e);
            throw new LuceneException(e);
        }
    }

    /**
     * 复用search
     * @return
     */
    public synchronized IndexSearcher getSearcher() {
        try {
            if(indexReader == null) {
                indexReader = DirectoryReader.open(directory);
            } else {
                // 如果 IndexReader 不为空，就使用 DirectoryReader 打开一个索引变更过的 IndexReader 类
                IndexReader tr = DirectoryReader.openIfChanged((DirectoryReader)indexReader);
                if(tr != null) {
                    indexReader.close();
                    indexReader = tr;
                }
            }
            return new IndexSearcher(indexReader);
        }  catch (IOException e) {
            logger.error("DirectoryReader openIfChanged failed", e);
            throw new LuceneException(e);
        }
    }

    @PreDestroy
    public void clear() {
        if(this.indexReader != null) {
            try {
                this.indexReader.close();
            } catch (IOException e) {
            }
        }
    }

    Term buildTerm(String key, String val) {
        return new Term(BaseIndexField.F_ID, val);
    }
}
